Calculate slow and fast exponential moving averages for AAPL stock using historical data, visualize results and calculate return and performance metrics. Our strategy purchases the stock as the fast exponential moving average crosses above the slow moving average. This system does not go short.
# Save apple data to a dataframe
df <- data.frame(Date=index(AAPL),coredata(AAPL))
# take most recent observations
df <- tail(df, 4000)
# reset index
rownames(df) = NULL
# Save DAL data to a dataframe
dal_df <- data.frame(Date=index(DAL),coredata(DAL))
# take most recent observations
dal <- tail(dal_df, 4000)
# reset index
rownames(dal) = NULL
# SPX to a dataframe
SPX <- data.frame(Date=index(GSPC),coredata(GSPC))
# take most recent observations
SPX <- tail(SPX, 4000)
# reset index
rownames(SPX) = NULL
# copy the data
raw_data = df
dal_df = dal
# rename AAPL columns for simplicity
names(raw_data)[names(raw_data)=="AAPL.Open"] = "Open"
names(raw_data)[names(raw_data)=="AAPL.High"] = "High"
names(raw_data)[names(raw_data)=="AAPL.Low"] = "Low"
names(raw_data)[names(raw_data)=="AAPL.Close"] = "Close"
names(raw_data)[names(raw_data)=="AAPL.Volume"] = "Volume"
names(raw_data)[names(raw_data)=="AAPL.Adjusted"] = "Price"
# rename DAL columns for simplicity
names(dal_df)[names(dal_df)=="DAL.Open"] = "Open"
names(dal_df)[names(dal_df)=="DAL.High"] = "High"
names(dal_df)[names(dal_df)=="DAL.Low"] = "Low"
names(dal_df)[names(dal_df)=="DAL.Close"] = "Close"
names(dal_df)[names(dal_df)=="DAL.Volume"] = "Volume"
names(dal_df)[names(dal_df)=="DAL.Adjusted"] = "Price"
# rename SPX columns for simplicity
names(SPX)[names(SPX)=="GSPC.Open"] = "Open"
names(SPX)[names(SPX)=="GSPC.High"] = "High"
names(SPX)[names(SPX)=="GSPC.Low"] = "Low"
names(SPX)[names(SPX)=="GSPC.Close"] = "Close"
names(SPX)[names(SPX)=="GSPC.Volume"] = "Volume"
names(SPX)[names(SPX)=="GSPC.Adjusted"] = "Price"
[1] "Our sample considers OHLC data from 2008-01-29 through 2023-12-15 for Apple, Delta and the S&P500"
summary(raw_data[,1:5], digits = 3)
Date Open High
Min. :2008-01-29 Min. : 2.84 Min. : 2.93
1st Qu.:2012-01-16 1st Qu.: 14.64 1st Qu.: 14.92
Median :2016-01-06 Median : 28.30 Median : 28.57
Mean :2016-01-06 Mean : 52.91 Mean : 53.49
3rd Qu.:2019-12-26 3rd Qu.: 66.26 3rd Qu.: 67.00
Max. :2023-12-15 Max. :198.02 Max. :199.62
Low Close
Min. : 2.79 Min. : 2.79
1st Qu.: 14.58 1st Qu.: 14.71
Median : 28.00 Median : 28.29
Mean : 52.36 Mean : 52.95
3rd Qu.: 65.64 3rd Qu.: 66.52
Max. :197.00 Max. :198.11
sd(raw_data$Close)
[1] 54.61935
summary(SPX[,1:5])
Date Open High
Min. :2008-01-29 Min. : 679.3 Min. : 695.3
1st Qu.:2012-01-16 1st Qu.:1354.3 1st Qu.:1362.3
Median :2016-01-06 Median :2088.8 Median :2096.9
Mean :2016-01-06 Mean :2351.7 Mean :2365.2
3rd Qu.:2019-12-26 3rd Qu.:3003.3 3rd Qu.:3014.7
Max. :2023-12-15 Max. :4804.5 Max. :4818.6
Low Close
Min. : 666.8 Min. : 676.5
1st Qu.:1343.3 1st Qu.:1354.6
Median :2079.2 Median :2088.7
Mean :2337.1 Mean :2352.1
3rd Qu.:2988.3 3rd Qu.:3003.4
Max. :4780.0 Max. :4796.6
sd(SPX$Close)
[1] 1108.915
summary(dal_df[,1:5])
Date Open High Low
Min. :2008-01-29 Min. : 3.90 Min. : 4.06 Min. : 3.51
1st Qu.:2012-01-16 1st Qu.:11.79 1st Qu.:12.04 1st Qu.:11.57
Median :2016-01-06 Median :36.03 Median :36.66 Median :35.35
Mean :2016-01-06 Mean :31.77 Mean :32.24 Mean :31.27
3rd Qu.:2019-12-26 3rd Qu.:46.89 3rd Qu.:47.43 3rd Qu.:46.20
Max. :2023-12-15 Max. :63.23 Max. :63.44 Max. :62.38
Close
Min. : 3.93
1st Qu.:11.80
Median :36.03
Mean :31.75
3rd Qu.:46.76
Max. :63.16
sd(dal_df$Close)
[1] 17.40375
Calculate a simple day-to-day return, from adjusted-close to adjusted-close and store in return and log_return vector:
# create a blank column
raw_data$return = NA
raw_data$log_return = NA
# loop through data and calculate daily returns
for(t in 2:nrow(raw_data)){
raw_data$return[t] = (raw_data$Price[t] - raw_data$Price[t-1])/ raw_data$Price[t-1]
raw_data$log_return[t] = log(raw_data$Price[t]/raw_data$Price[t-1])
}
# Do the same for DAL
dal_df$return = NA
dal_df$log_return = NA
for(t in 2:nrow(dal_df)){
dal_df$return[t] = (dal_df$Price[t] - dal_df$Price[t-1])/ dal_df$Price[t-1]
dal_df$log_return[t] = log(dal_df$Price[t]/dal_df$Price[t-1])
}
# Do the same for SP500
SPX$return = NA
SPX$log_return = NA
for(t in 2:nrow(SPX)){
SPX$return[t] = (SPX$Price[t] - SPX$Price[t-1])/ SPX$Price[t-1]
SPX$log_return[t] = log(SPX$Price[t]/SPX$Price[t-1])
}
head(subset(raw_data, select = c("Date", "Close", "return", "log_return"))) |>
gt()
| Date | Close | return | log_return |
|---|---|---|---|
| 2008-01-29 | 4.697857 | NA | NA |
| 2008-01-30 | 4.720714 | 0.004865325 | 0.004853528 |
| 2008-01-31 | 4.834286 | 0.024058165 | 0.023773327 |
| 2008-02-01 | 4.776786 | -0.011894262 | -0.011965564 |
| 2008-02-04 | 4.701786 | -0.015700991 | -0.015825557 |
| 2008-02-05 | 4.620000 | -0.017394658 | -0.017547723 |
| Date | Close | return | log_return |
|---|---|---|---|
| 2008-01-29 | 16.01 | NA | NA |
| 2008-01-30 | 16.15 | 0.008744688 | 0.008706675 |
| 2008-01-31 | 16.82 | 0.041485849 | 0.040648395 |
| 2008-02-01 | 18.53 | 0.101665131 | 0.096822791 |
| 2008-02-04 | 17.25 | -0.069077528 | -0.071579279 |
| 2008-02-05 | 17.03 | -0.012753526 | -0.012835550 |
| Date | Close | return | log_return |
|---|---|---|---|
| 2008-01-29 | 1362.30 | NA | NA |
| 2008-01-30 | 1355.81 | -0.004763995 | -0.004775379 |
| 2008-01-31 | 1378.55 | 0.016772254 | 0.016633153 |
| 2008-02-01 | 1395.42 | 0.012237492 | 0.012163219 |
| 2008-02-04 | 1380.82 | -0.010462869 | -0.010517990 |
| 2008-02-05 | 1336.64 | -0.031995433 | -0.032518473 |
Create a function to calculate two simple moving averages of differing lengths for use for a trend-following trading system (for this system we won’t be using a simple moving average):
Two_MovAvg_function = function(variable, slow_period = 100, fast_period = 20){
# blank list of variables
fast_ma = rep(NA, length(variable))
slow_ma = rep(NA, length(variable))
# loop to calculate average price for each record
for (t in (slow_period+1):length(variable)){
est_slow = mean(variable[(t-(slow_period+1)):(t-1)])
est_fast = mean(variable[(t-(fast_period+1)):(t-1)])
# update current average price
fast_ma[t] = est_fast
slow_ma[t] = est_slow
}
# create a dataframe for new vectors
ma_data = data.frame(fast_ma = fast_ma,
slow_ma = slow_ma)
return(ma_data)
}
Create a function to calculate two exponential moving averages of differing lengths for use for a trend-following trading system and store in fast_ma, slow_ma and ema_diff vector:
EA_Mov_Avg = function(variable, slow_lag = 100, fast_lag = 25){
fast_ea = variable
slow_ea = variable
ema_diff = rep(NA, length(variable))
# Loop to calculate each exponential average
for (t in 2:length(variable)){
est_slow = slow_ea[t-1] + (slow_ea[t] - slow_ea[t-1]) / ((slow_lag + 1) / 2)
est_fast = fast_ea[t-1] + (fast_ea[t] - fast_ea[t-1]) / ((fast_lag + 1) / 2)
ema_diff[t] = est_slow - est_fast
slow_ea[t] = est_slow
fast_ea[t] = est_fast
}
# dataframe for new vector
ema_data = data.frame(fast_ema = fast_ea,
slow_ema = slow_ea,
ema_diff = ema_diff)
return(ema_data)
}
# add these as new vectors to original data
new_df = EA_Mov_Avg(raw_data$Close, slow_lag = 150, fast_lag = 25)
new_dal_df = EA_Mov_Avg(dal_df$Close, slow_lag = 150, fast_lag = 25)
# Apple
raw_data$slow_ma = new_df$slow_ema
raw_data$fast_ma = new_df$fast_ema
raw_data$ema_diff = new_df$ema_diff
# DAL
dal_df$slow_ma = new_dal_df$slow_ema
dal_df$fast_ma = new_dal_df$fast_ema
dal_df$ema_diff = new_dal_df$ema_diff
tail(raw_data)
Date Open High Low Close Volume Price
3995 2023-12-08 194.20 195.99 193.67 195.71 53377300 195.71
3996 2023-12-11 193.11 193.49 191.42 193.18 60943700 193.18
3997 2023-12-12 193.08 194.72 191.72 194.71 52696900 194.71
3998 2023-12-13 195.09 198.00 194.85 197.96 70404200 197.96
3999 2023-12-14 198.02 199.62 196.16 198.11 66831600 198.11
4000 2023-12-15 197.53 198.40 197.00 197.57 128256700 197.57
return log_return slow_ma fast_ma ema_diff
3995 0.007412377 0.0073850400 178.4263 188.2086 -9.782240
3996 -0.012927362 -0.0130116472 178.6217 188.5910 -9.969244
3997 0.007920148 0.0078889478 178.8348 189.0617 -10.226848
3998 0.016691489 0.0165537174 179.0881 189.7462 -10.658021
3999 0.000757698 0.0007574111 179.3401 190.3895 -11.049447
4000 -0.002725725 -0.0027294461 179.5816 190.9419 -11.360335
tail(dal_df)
Date Open High Low Close Volume Price return
3995 2023-12-08 40.26 40.74 39.86 40.35 8723000 40.35 0.0002478513
3996 2023-12-11 40.41 40.63 40.11 40.51 7295400 40.51 0.0039653000
3997 2023-12-12 40.52 41.46 40.52 41.24 8448900 41.24 0.0180203255
3998 2023-12-13 40.87 41.36 40.12 41.19 12821300 41.19 -0.0012124891
3999 2023-12-14 41.53 42.70 41.49 42.44 14530800 42.44 0.0303471727
4000 2023-12-15 42.59 42.59 41.98 42.34 12902100 42.34 -0.0023562318
log_return slow_ma fast_ma ema_diff
3995 0.0002478206 37.69339 36.66494 1.02844868
3996 0.0039574589 37.73070 36.96072 0.76998124
3997 0.0178598841 37.77718 37.28989 0.48728631
3998 -0.0012132247 37.82238 37.58990 0.23248104
3999 0.0298958063 37.88354 37.96298 -0.07944294
4000 -0.0023590121 37.94257 38.29968 -0.35711049
First calculate the maximum of the aforementioned ranges and store in max_range vector:
max_range_functon = function(variable){
max_range = rep(0, length(variable))
for (t in 2:length(variable$Price)){
max_rng = max(abs(variable$High[t] - variable$Low[t]), abs(variable$High[t] - variable$Close[t-1]), abs(variable$Close[t-1] - variable$Low[t]))
max_range[t] = max_rng
}
max_range_data = data.frame(max_range = max_range)
return(max_range_data)
}
# Test Function
# Implement on full APPL dataset
max_range_df = max_range_functon(raw_data)
# Implement on full DAL dataset
max_range_dal_df = max_range_functon(dal_df)
# Create new variable in data
raw_data$max_range = max_range_df$max_range
dal_df$max_range = max_range_dal_df$max_range
subset(raw_data[25:32,], select = c("Date", "Open", "High", "Low", "Close", "max_range"))
Date Open High Low Close max_range
25 2008-03-04 4.356786 4.460000 4.300000 4.450714 0.1599998
26 2008-03-05 4.413571 4.469286 4.366071 4.446071 0.1032147
27 2008-03-06 4.450357 4.553571 4.314643 4.318929 0.2389283
28 2008-03-07 4.300357 4.392143 4.251786 4.366071 0.1403565
29 2008-03-10 4.356429 4.409286 4.263214 4.274643 0.1460719
30 2008-03-11 4.432143 4.552857 4.357143 4.548214 0.2782140
31 2008-03-12 4.537143 4.595714 4.470357 4.501071 0.1253572
32 2008-03-13 4.432143 4.625000 4.392857 4.569286 0.2321429
Date Open High Low Close max_range
25 2008-03-04 12.85 13.53 12.51 13.14 1.020000
26 2008-03-05 13.19 14.60 12.87 14.33 1.730000
27 2008-03-06 14.15 14.45 13.34 13.50 1.110000
28 2008-03-07 13.13 13.75 12.71 12.89 1.040000
29 2008-03-10 12.92 13.26 11.95 11.98 1.310000
30 2008-03-11 12.16 12.64 11.64 12.11 1.000000
31 2008-03-12 11.58 11.80 10.05 10.13 2.059999
32 2008-03-13 9.82 10.72 9.56 10.52 1.160000
Create function that uses the true range vector to calculate a moving average of the true range with a user-specified lag period and store in atr vector:
ATR_function = function(variable, lag = 15){
atr_list = rep(NA, length(variable))
# Loop to create ATR
for (t in (lag+2):length(variable)){
est_atr = mean(variable[(t-(lag)):t])
atr_list[t] = est_atr
}
atr_data = data.frame(atr = atr_list)
return(atr_data)
}
# Add vector to our data
## AAPL
atr_data = ATR_function(raw_data$max_range)
raw_data$atr = atr_data$atr
## DAL
atr_dal_data = ATR_function(dal_df$max_range)
dal_df$atr = atr_dal_data$atr
This is a multiple of the ATR for volatility-based position sizing:
Store in risk_per_lot vector:
Close max_range atr risk_per_lot
200 3.424286 0.2107141 0.2431697 1.2158484
201 3.384643 0.1753569 0.2398661 1.1993305
202 3.218571 0.1700001 0.2342411 1.1712056
203 3.444286 0.3721430 0.2357144 1.1785718
204 3.222857 0.2300000 0.2336831 1.1684156
205 3.147857 0.1174998 0.2229019 1.1145093
206 3.211071 0.1475000 0.2192411 1.0962056
207 3.081786 0.1917851 0.2124554 1.0622769
208 2.874643 0.2303572 0.2053571 1.0267857
209 2.949286 0.1778572 0.1994196 0.9970982
210 3.319643 0.4360709 0.2135045 1.0675223
Close max_range atr risk_per_lot
200 8.98 0.8299999 1.316250 6.581250
201 8.84 0.6099997 1.303125 6.515625
202 7.37 1.7300000 1.349375 6.746875
203 8.17 0.9800000 1.281875 6.409376
204 7.85 0.6999998 1.232500 6.162500
205 7.87 0.7000003 1.178125 5.890626
206 7.88 0.8799996 1.150000 5.750000
207 7.00 1.0400004 1.138750 5.693751
208 7.02 1.1199999 1.163750 5.818750
209 6.82 1.1900001 1.140625 5.703125
210 7.35 0.9099998 1.041250 5.206250
# save moving average cross-over dates for chart annotations
h = raw_data[which((raw_data$fast_ma > raw_data$slow_ma) & (raw_data$fast_ma[-1] < raw_data$slow_ma[-1]))+1,]
l = raw_data[which((raw_data$fast_ma < raw_data$slow_ma) & (raw_data$fast_ma[-1] > raw_data$slow_ma[-1]))+1,]
h = na.exclude(h)
h
Date Open High Low Close
161 2008-09-16 4.780714 5.089286 4.719643 4.995714
1208 2012-11-12 19.791071 19.803572 19.237499 19.386786
1897 2015-08-10 29.132500 29.997499 29.132500 29.930000
2071 2016-04-19 26.969999 27.000000 26.557501 26.727501
2726 2018-11-21 44.932499 45.067501 44.137501 44.195000
2857 2019-06-04 43.860001 44.957500 43.630001 44.910000
3063 2020-03-27 63.187500 63.967499 61.762501 61.935001
3598 2022-05-11 153.500000 155.449997 145.809998 146.500000
3693 2022-09-27 152.740005 154.720001 149.949997 151.759995
3967 2023-10-30 169.020004 171.169998 168.869995 170.289993
Volume Price return log_return slow_ma
161 1199836400 4.234765 -0.003419769 -0.003425630 5.747746
1208 515802000 16.579727 -0.007732053 -0.007762100 21.457792
1897 219806400 27.131924 0.036357311 0.035711979 30.521466
2071 129539600 24.464561 -0.005303275 -0.005317388 26.907706
2726 124496800 42.418842 -0.001130032 -0.001130671 50.357920
2857 123872000 43.456684 0.036583821 0.035930519 46.671909
3063 204216800 60.482265 -0.041402250 -0.042283739 66.466317
3598 142689800 145.242661 -0.051841208 -0.053233289 161.335502
3693 84442700 150.666519 0.006566277 0.006544813 156.180428
3967 51131000 170.065933 0.012305221 0.012230127 174.363520
fast_ma ema_diff max_range atr risk_per_lot
161 5.722272 0.0254730475 0.3696427 0.2368751 1.184375
1208 21.451680 0.0061117641 0.5660725 0.6715622 3.357811
1897 30.497427 0.0240385186 1.1175003 0.7928125 3.964062
2071 26.907096 0.0006101968 0.4424992 0.5092187 2.546093
2726 50.248957 0.1089631330 0.9300003 1.8137500 9.068750
2857 46.601018 0.0708910523 1.6324997 1.3182809 6.591405
3063 66.384702 0.0816144377 2.8474998 4.9378126 24.689063
3598 160.683833 0.6516691677 9.6399994 6.2474985 31.237493
3693 156.138959 0.0414687757 4.7700043 4.6318750 23.159375
3967 174.234966 0.1285538854 2.9499969 3.0418768 15.209384
l
Date Open High Low Close
40 2008-03-26 5.031071 5.205000 5.022857 5.180714
304 2009-04-13 4.286071 4.320714 4.250000 4.293571
1399 2013-08-16 17.862499 17.962143 17.816429 17.940357
2069 2016-04-15 28.027500 28.075001 27.432501 27.462500
2148 2016-08-08 26.879999 27.092501 26.790001 27.092501
2809 2019-03-26 47.915001 48.220001 46.145000 46.697498
2861 2019-06-10 47.952499 48.842499 47.904999 48.145000
3076 2020-04-16 71.845001 72.050003 70.587502 71.672501
3656 2022-08-04 166.009995 167.190002 164.429993 165.809998
3790 2023-02-15 153.110001 155.500000 152.880005 155.330002
3971 2023-11-03 174.240005 176.820007 173.350006 176.649994
Volume Price return log_return slow_ma
40 1182084400 4.391588 0.028940012 0.028529158 4.638723
304 389236400 3.639573 0.005435969 0.005421247 3.826802
1399 362306000 15.636861 0.008876784 0.008837617 16.415864
2069 187756000 25.137333 -0.020071315 -0.020275480 26.910664
2148 112148800 25.084808 0.008280810 0.008246712 25.439263
2809 199202000 45.012993 -0.010331733 -0.010385476 45.161955
2861 104883600 46.586994 0.012779220 0.012698255 46.684806
3076 157125200 69.991371 0.007945865 0.007914463 66.394240
3656 55474100 164.386932 -0.001926246 -0.001928103 153.529224
3790 65573800 154.702438 0.013903323 0.013807558 146.176338
3971 79763700 176.417572 -0.005181159 -0.005194628 174.384907
fast_ma ema_diff max_range atr risk_per_lot
40 4.641450 -0.0027265677 0.1821427 0.1907812 0.9539059
304 3.838849 -0.0120475123 0.0707140 0.1428793 0.7143966
1399 16.450733 -0.0348689245 0.1796436 0.3495760 1.7478800
2069 26.926401 -0.0157370506 0.6424999 0.4750001 2.3750007
2148 25.440247 -0.0009847918 0.3024998 0.4473438 2.2367191
2809 45.256133 -0.0941787961 2.0750008 1.0417192 5.2085960
2861 46.708433 -0.0236272637 1.3050003 1.2028127 6.0140634
3076 66.711820 -0.3175799840 1.4625015 2.6524994 13.2624972
3656 153.556168 -0.0269442059 2.7600098 3.9068747 19.5343733
3790 146.585439 -0.4091013714 2.6199951 3.9224977 19.6124887
3971 174.430541 -0.0456339127 4.2200012 3.3275013 16.6375065
# save moving average cross-over dates for chart annotations
hi = dal_df[which((dal_df$fast_ma > dal_df$slow_ma) & (dal_df$fast_ma[-1] < dal_df$slow_ma[-1]))+1,]
lo = dal_df[which((dal_df$fast_ma < dal_df$slow_ma) & (dal_df$fast_ma[-1] > dal_df$slow_ma[-1]))+1,]
hi = na.exclude(hi)
hi
Date Open High Low Close Volume Price
24 2008-03-03 13.27 13.35 12.50 12.98 4713200 11.473167
204 2008-11-14 8.09 8.46 7.76 7.85 8104200 6.938702
258 2009-02-04 7.00 7.12 6.50 6.51 11712200 5.754261
624 2010-07-20 11.18 11.62 11.10 11.53 9408900 10.191497
753 2011-01-21 11.71 11.78 11.39 11.54 15559900 10.200336
1138 2012-08-01 9.49 9.62 9.40 9.48 15043400 8.379478
1690 2014-10-13 33.30 33.31 30.12 30.90 41248000 27.618721
1853 2015-06-08 42.37 42.62 40.65 40.75 18359700 36.649052
2015 2016-01-28 44.84 45.00 42.52 43.20 13996900 39.069405
2074 2016-04-22 43.43 44.67 43.16 44.62 14258600 40.481609
2318 2017-04-11 45.00 45.32 44.49 45.29 10506800 41.805252
2413 2017-08-25 45.43 47.06 45.35 46.68 13938800 43.545128
2589 2018-05-09 52.17 52.28 51.46 51.62 8403900 48.732548
2623 2018-06-27 51.03 51.18 49.70 49.89 9035800 47.379257
2702 2018-10-18 54.29 54.30 52.96 53.13 5871200 50.800251
2746 2018-12-21 50.51 51.28 49.29 49.45 11680200 47.578712
2946 2019-10-09 54.01 54.40 53.59 53.92 7615300 52.895596
2986 2019-12-05 56.39 56.41 55.70 55.88 3899900 55.225609
3041 2020-02-26 51.44 51.77 49.00 49.59 15985900 49.347950
3391 2021-07-16 41.83 42.05 39.84 40.06 16726600 39.864468
3456 2021-10-18 40.67 41.41 40.52 41.01 10140200 40.809826
3550 2022-03-03 38.49 38.74 36.38 36.56 17589100 36.381550
3607 2022-05-24 38.82 38.95 36.78 37.22 10856500 37.038326
3615 2022-06-06 39.00 39.29 38.31 39.00 12330400 38.809635
3752 2022-12-20 32.86 33.28 32.75 32.90 5995900 32.739414
3816 2023-03-24 31.74 31.86 31.04 31.59 12647300 31.435808
3944 2023-09-27 36.77 37.15 36.53 36.66 7996900 36.557686
return log_return slow_ma fast_ma ema_diff
24 -0.0277155297 -0.0281068524 16.137392 15.942950 0.194442154
204 -0.0391678329 -0.0399555292 8.792714 8.790646 0.002067308
258 -0.0565219322 -0.0581821600 9.314662 9.281247 0.033415222
624 0.0131811903 0.0130950743 12.138142 12.121677 0.016464694
753 -0.0060291583 -0.0060474071 12.441692 12.429321 0.012371028
1138 -0.0176167825 -0.0177738049 10.136524 10.119460 0.017063860
1690 -0.0610758691 -0.0630206008 36.256417 36.147118 0.109298469
1853 -0.0501165735 -0.0514160109 43.967611 43.864808 0.102802775
2015 -0.0339892682 -0.0345803353 47.192733 46.987755 0.204978627
2074 -0.0077830791 -0.0078135253 47.098074 47.017579 0.080495053
2318 0.0071159814 0.0070907823 46.402673 46.363002 0.039671307
2413 0.0325150466 0.0319976188 49.272102 49.117258 0.154844325
2589 -0.0143210699 -0.0144246061 53.213014 53.148667 0.064346206
2623 -0.0231056402 -0.0233767599 53.365226 53.318601 0.046625021
2702 -0.0215469382 -0.0217824628 54.346382 54.271469 0.074913582
2746 -0.0286781382 -0.0290973910 54.838522 54.693107 0.145415298
2946 0.0135337002 0.0134429376 56.682551 56.506699 0.175852033
2986 -0.0032108907 -0.0032160566 56.330176 56.322690 0.007486750
3041 -0.0255452344 -0.0258771791 57.180516 56.932703 0.247812371
3391 -0.0311969922 -0.0316939821 43.547324 43.329234 0.218090202
3456 0.0004878033 0.0004876843 42.211823 42.203141 0.008681967
3550 -0.0389063741 -0.0396834493 40.603786 40.411866 0.191920582
3607 -0.0581984495 -0.0599606948 39.856313 39.740031 0.116281668
3615 0.0119355868 0.0118649194 39.874651 39.841234 0.033417579
3752 -0.0006073834 -0.0006075679 33.944100 33.929391 0.014709243
3816 -0.0171126373 -0.0172607507 35.623545 35.506664 0.116881284
3944 -0.0029915316 -0.0029960152 40.151455 39.992967 0.158487997
max_range atr risk_per_lot
24 0.8500004 0.9524999 4.762499
204 0.6999998 1.2325001 6.162500
258 0.6199999 1.1000001 5.500000
624 0.5199995 0.6918748 3.459374
753 0.3899994 0.4356251 2.178125
1138 0.2500000 0.4187499 2.093750
1690 3.1900005 1.4331254 7.165627
1853 2.2500000 1.4418755 7.209377
2015 2.4799995 2.0868747 10.434374
2074 1.8100014 1.3693745 6.846873
2318 0.8299980 0.9218750 4.609375
2413 1.8500023 1.0218759 5.109379
2589 0.9099998 1.2231250 6.115625
2623 1.4799995 1.2256248 6.128124
2702 1.3400002 1.4993751 7.496876
2746 1.9899979 1.8862500 9.431250
2946 1.2000008 1.3774996 6.887498
2986 0.7099991 0.9981256 4.990628
3041 2.7700005 1.7006252 8.503126
3391 2.2099991 1.3481250 6.740625
3456 0.8899994 1.3131254 6.565627
3550 2.3600006 1.9406245 9.703122
3607 2.7400017 1.9162505 9.581252
3615 0.9799995 1.8456254 9.228127
3752 0.5299988 1.1831257 5.915629
3816 1.0999985 1.3850003 6.925002
3944 0.6200027 0.9112499 4.556249
lo = na.exclude(lo)
lo
Date Open High Low Close Volume Price
196 2008-11-04 11.08 12.00 10.03 11.28 24872000 9.970520
220 2008-12-09 10.78 11.64 10.53 10.87 18926300 9.608115
400 2009-08-27 7.48 7.50 7.25 7.49 10430300 6.620495
690 2010-10-21 13.03 13.54 12.90 13.53 20095600 11.959318
1004 2012-01-20 9.30 9.50 9.25 9.41 7844600 8.317604
1195 2012-10-22 10.00 10.20 9.90 10.14 7317200 8.962861
1700 2014-10-27 39.38 40.10 39.20 39.75 18958800 35.528942
1889 2015-07-29 44.81 45.33 43.85 44.18 10355300 39.733871
2039 2016-03-03 48.59 49.01 48.35 48.79 8853700 44.264862
2210 2016-11-03 42.06 42.57 41.77 41.92 8142800 38.364182
2334 2017-05-04 48.46 48.80 48.20 48.64 7467400 44.897480
2442 2017-10-06 51.40 52.52 51.40 52.01 6735400 48.517170
2600 2018-05-24 53.53 54.57 53.53 54.40 5251200 51.662292
2651 2018-08-07 54.60 55.09 54.52 54.70 4425300 52.301399
2714 2018-11-05 56.02 56.38 55.55 56.05 5206000 53.592197
2817 2019-04-05 57.44 58.20 57.33 57.73 6918300 55.933582
2981 2019-11-27 57.53 57.58 56.68 57.07 4062500 56.401676
2993 2019-12-16 57.17 58.49 57.15 58.42 7460700 57.735863
3223 2020-11-12 34.09 35.55 34.02 34.38 21676300 34.212189
3447 2021-10-05 44.99 45.56 44.46 44.74 9596000 44.521622
3538 2022-02-14 42.38 43.25 41.71 41.97 11581600 41.765141
3584 2022-04-21 44.81 46.27 44.54 44.73 31849600 44.511669
3610 2022-05-27 41.00 42.23 41.00 42.23 10730600 42.023872
3738 2022-11-30 34.51 35.38 34.21 35.37 7404100 35.197357
3764 2023-01-09 36.54 37.42 36.35 36.77 11251300 36.590527
3863 2023-06-01 36.42 36.81 36.00 36.38 7769000 36.202427
3999 2023-12-14 41.53 42.70 41.49 42.44 14530800 42.439999
return log_return slow_ma fast_ma ema_diff
196 -0.0208333724 -0.0210534491 8.791047 8.890360 -0.099312565
220 -0.0136115943 -0.0137050813 8.747561 8.871540 -0.123978743
400 0.0040214562 0.0040133917 6.900195 6.930079 -0.029884534
690 0.0431764829 0.0422703687 11.713393 11.730301 -0.016907856
1004 0.0085748591 0.0085383038 8.547007 8.562098 -0.015091284
1195 0.0140002460 0.0139031478 9.778188 9.798855 -0.020667766
1700 0.0078602438 0.0078295130 36.246599 36.426752 -0.180152894
1889 -0.0162545673 -0.0163881220 43.440206 43.444856 -0.004649746
2039 0.0049433218 0.0049311437 46.839241 46.900670 -0.061428635
2210 -0.0002384849 -0.0002385133 40.537933 40.600753 -0.062820548
2334 0.0068307676 0.0068075436 46.334027 46.377038 -0.043010701
2442 0.0063852145 0.0063649154 49.020699 49.140316 -0.119616838
2600 0.0187265557 0.0185533725 53.202877 53.215520 -0.012642347
2651 0.0051451150 0.0051319242 52.954865 52.959755 -0.004889844
2714 -0.0012475099 -0.0012482887 54.339901 54.442930 -0.103028884
2817 0.0089130214 0.0088735348 52.148067 52.183860 -0.035793478
2981 -0.0067872694 -0.0068104076 56.331252 56.349205 -0.017952896
2993 0.0297901461 0.0293550399 56.332870 56.381922 -0.049052243
3223 -0.0182752442 -0.0184442993 32.438902 32.526346 -0.087443534
3447 -0.0048932230 -0.0049052340 42.186628 42.191977 -0.005349117
3538 -0.0085046783 -0.0085410494 40.566451 40.641506 -0.075054663
3584 0.0273311264 0.0269643000 39.631573 39.784479 -0.152905831
3610 0.0342884901 0.0337137412 39.884276 39.934239 -0.049963328
3738 0.0219589725 0.0217213466 33.897592 33.929432 -0.031840072
3764 0.0205385795 0.0203305071 33.925796 34.025409 -0.099612477
3863 0.0013762495 0.0013753033 35.093709 35.099561 -0.005852164
3999 0.0303471727 0.0298958063 37.883542 37.962985 -0.079442944
max_range atr risk_per_lot
196 1.9700003 1.3193751 6.596875
220 1.1100006 1.0050000 5.025000
400 0.2500000 0.3500000 1.750000
690 0.6400003 0.4850000 2.425000
1004 0.2500000 0.3181249 1.590625
1195 0.3000002 0.3274999 1.637499
1700 0.8999977 1.8275003 9.137502
1889 1.4800034 1.2612500 6.306250
2039 0.6599998 1.2812495 6.406248
2210 0.7999992 1.1793752 5.896876
2334 0.5999985 1.0999992 5.499996
2442 1.1199989 1.0718753 5.359377
2600 1.1699982 1.0900004 5.450002
2651 0.6700020 1.1387506 5.693753
2714 0.8300018 1.6181254 8.090627
2817 0.9799995 1.2262497 6.131248
2981 0.9000015 0.8443754 4.221877
2993 1.7600021 1.0968750 5.484375
3223 1.5299988 1.9068747 9.534373
3447 1.1000023 1.3125007 6.562504
3538 1.5400009 1.6268749 8.134375
3584 2.7299995 1.4668751 7.334375
3610 1.3999977 1.9500005 9.750003
3738 1.1700020 1.0193758 5.096879
3764 1.3899994 1.0831250 5.415625
3863 0.8100014 1.0481250 5.240625
3999 1.5100021 1.0112507 5.056254
# Create arrow annotations for trade entry signal
# AAPL
# Upper annotations
h_a <- list(
x = h$Date,
y = h$slow_ma,
text = "Sell",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "red",
arrowhead = 5,
arrowsize = 0.8,
arrowwidth = 1.5,
opacity = 0.75,
align = "left",
ax = 5,
ay = -55
)
# Lower annotations
l_a <- list(
x = l$Date,
y = l$slow_ma,
text = "Buy",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "green",
arrowhead = 5,
arrowsize = 0.8,
arrowwidth = 1.5,
opacity = 0.75,
align = "right",
ax = -5,
ay = 55
)
# figure labels
f <- list(
family = "Courier New, monospace",
size = 18,
color = "#7f7f7f ")
x <- list(
title = "x Axis",
titlefont = f)
y <- list(
title = "y Axis",
titlefont = f)
# DAL
# Upper annotations
h_a_dal <- list(
x = hi$Date,
y = hi$slow_ma,
text = "Sell",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "red",
arrowhead = 5,
arrowsize = 0.8,
arrowwidth = 1.5,
opacity = 0.75,
align = "left",
ax = 5,
ay = -55
)
# Lower annotations
l_a_dal <- list(
x = lo$Date,
y = lo$slow_ma,
text = "Buy",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "green",
arrowhead = 5,
arrowsize = 0.8,
arrowwidth = 1.5,
opacity = 0.75,
align = "right",
ax = -5,
ay = 55
)
# figure labels
f <- list(
family = "Courier New, monospace",
size = 18,
color = "#7f7f7f ")
x <- list(
title = "x Axis",
titlefont = f)
y <- list(
title = "y Axis",
titlefont = f)
The following candlestick chart helps us to visualize what our strategy is doing
As the faster price smoothing crosses above the slower lag, the system will buy on that day.
raw_data$MACD_direction = rep(NA, length(raw_data$Date))
# colors column for increasing and decreasing
for (i in 2:length(raw_data[,1])) {
if (raw_data$ema_diff[i] > 0) {
raw_data$MACD_direction[i] = 'Increasing'
} else {
raw_data$MACD_direction[i] = 'Decreasing'
}
}
# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing
# Plot main data
fig <- raw_data %>% plot_ly(x = ~Date, type="ohlc",
open = ~Open, close = ~Close,
high = ~High, low = ~Low, name = "AAPL",
increasing = i, decreasing = d)
# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
line = list(color = '#ccc', width = 0.5),
legendgroup = "Bands", inherit = F,
showlegend = TRUE, hoverinfo = "none")
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
line = list(color = '#E377C2', width = 0.5),
hoverinfo = "none", inherit = F)
# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))
# plot MACD line subplot
fig2 <- raw_data
fig2 <- fig2 %>% plot_ly(x=~Date, y=~ema_diff, type='bar', name = "MACD",
color = ~MACD_direction, colors = c("red", "green"), alpha = 0.8)
fig2 <- fig2 %>% layout(yaxis = list(title = "MACD"))
# use subplot to add plots to the same figure
fig = subplot(fig, fig2, heights = c(0.8,0.15), nrows = 2,
shareX = TRUE, titleY = TRUE)
# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)
fig <- fig %>% layout(annotations = l_a)
# Add title
fig <- fig %>% layout(title = "<b> AAPL - Trend Following System </b>",
xaxis = list(rangeslider = list(visible = F)))
fig
dal_df$MACD_direction = rep(NA, length(dal_df$Date))
# colors column for increasing and decreasing
for (i in 2:length(dal_df[,1])) {
if (dal_df$ema_diff[i] > 0) {
dal_df$MACD_direction[i] = 'Increasing'
} else {
dal_df$MACD_direction[i] = 'Decreasing'
}
}
# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing
# Plot main data
fig3 <- dal_df %>% plot_ly(x = ~Date, type="ohlc",
open = ~Open, close = ~Close,
high = ~High, low = ~Low, name = "DAL",
increasing = i, decreasing = d)
# Add Fast and Slow moving average lines
fig3 <- fig3 %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
line = list(color = '#ccc', width = 0.5),
legendgroup = "Bands", inherit = F,
showlegend = TRUE, hoverinfo = "none")
fig3 <- fig3 %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
line = list(color = '#E377C2', width = 0.5),
hoverinfo = "none", inherit = F)
# Add y-axis title
fig3 <- fig3 %>% layout(yaxis = list(title = "Price"))
# plot MACD line subplot
fig4 <- dal_df
fig4 <- fig4 %>% plot_ly(x=~Date, y=~ema_diff, type='bar', name = "MACD",
color = ~MACD_direction, colors = c("red", "green"), alpha = 0.8)
fig4 <- fig4 %>% layout(yaxis = list(title = "MACD"))
# use subplot to add plots to the same figure
fig3 = subplot(fig3, fig4, heights = c(0.8,0.15), nrows = 2,
shareX = TRUE, titleY = TRUE)
# Add arrow annotations
fig3 <- fig3 %>% layout(annotations = h_a_dal)
fig3 <- fig3 %>% layout(annotations = l_a_dal)
# Add title
fig3 <- fig3 %>% layout(title = "<b> DAL Stock - Moving Average Crossover </b>",
xaxis = list(rangeslider = list(visible = F)))
fig3
Now we use our indicator to generate trading signals and store them in signal vector:
raw_data$signal = NA
# Loop through data to create signal column
for(t in 100:nrow(raw_data)-1){
current_slow = raw_data$slow_ma[t]
current_fast = raw_data$fast_ma[t]
prev_slow = raw_data$slow_ma[t-1]
prev_fast = raw_data$fast_ma[t-1]
# use 'if' statement to translate crossover into signals
if(current_fast > current_slow & prev_fast < prev_slow) {signal = "B"} else
if(current_fast < current_slow & prev_fast > prev_slow) {signal = "S"} else
{signal = "H"}
raw_data$signal[t+1] = signal
}
subset(raw_data[200:210,], select = c("Date","Close", "slow_ma", "fast_ma", "signal"))
Date Close slow_ma fast_ma signal
200 2008-11-10 3.424286 4.944619 3.739298 H
201 2008-11-11 3.384643 4.923957 3.712016 H
202 2008-11-12 3.218571 4.901369 3.674059 H
203 2008-11-13 3.444286 4.882070 3.656384 H
204 2008-11-14 3.222857 4.860094 3.623036 H
205 2008-11-17 3.147857 4.837415 3.586484 H
206 2008-11-18 3.211071 4.815874 3.557606 H
207 2008-11-19 3.081786 4.792906 3.521004 H
208 2008-11-20 2.874643 4.767498 3.471284 H
209 2008-11-21 2.949286 4.743416 3.431130 H
210 2008-11-24 3.319643 4.724558 3.422555 H
dal_df$signal = NA
# Loop through data to create signal column
for(t in 100:nrow(dal_df)-1){
current_slow = dal_df$slow_ma[t]
current_fast = dal_df$fast_ma[t]
prev_slow = dal_df$slow_ma[t-1]
prev_fast = dal_df$fast_ma[t-1]
# use 'if' statement to translate crossover into signals
if(current_fast > current_slow & prev_fast < prev_slow) {signal = "B"} else
if(current_fast < current_slow & prev_fast > prev_slow) {signal = "S"} else
{signal = "H"}
dal_df$signal[t+1] = signal
}
subset(dal_df[200:210,], select = c("Date","Close", "slow_ma", "fast_ma", "signal"))
Date Close slow_ma fast_ma signal
200 2008-11-10 8.98 8.833187 9.075149 H
201 2008-11-11 8.84 8.833277 9.057060 H
202 2008-11-12 7.37 8.813896 8.927286 H
203 2008-11-13 8.17 8.805368 8.869034 H
204 2008-11-14 7.85 8.792714 8.790646 H
205 2008-11-17 7.87 8.780492 8.719828 S
206 2008-11-18 7.88 8.768565 8.655225 H
207 2008-11-19 7.00 8.745141 8.527900 H
208 2008-11-20 7.02 8.722291 8.411908 H
209 2008-11-21 6.82 8.697095 8.289454 H
210 2008-11-24 7.35 8.679253 8.217188 H
After generating the signals, we must calculate the return from our strategy:
Store in investment_return vector
# used to calculate fill price penalty / skid
# we will penalize by 40% from the open - high for buy orders
# and 40% from the open - low for sell orders
buy_weight = .35
sell_weight = -.35
# Generate a holding status column
# and investment return column
raw_data$holding = 0 # first record, you are not holding
raw_data$Inv_return = NA # obviously the return will also be 0
# we can begin the loop
for (t in 100:nrow(raw_data)){
if(t==1){prev_holding=0}
else {prev_holding=raw_data$holding[t-1]}
# look at the signal to decide the change of holding status
signal = raw_data$signal[t]
if(signal=="B"){raw_data$holding[t]=1} else
if (signal=="S") {raw_data$holding[t]=0} else
if (signal=="H"){raw_data$holding[t]=prev_holding}
# Now calculate investment return
Open = raw_data$Open[t]
Close = raw_data$Close[t]
High = raw_data$High[t]
Low = raw_data$Low[t]
if(prev_holding==0 & signal=="B"){investment_return=Close/((High-Open)*buy_weight+Open) - 1} else
if(prev_holding==0 & signal=="H"){investment_return=0} else
#if(prev_holding==1 & signal=="S"){investment_return=Close[t-1]/(Open*(1+sell_weight)) - 1} else
#if(prev_holding==0 & signal=="S"){investment_return=(-1)*(Close/(Open*(1+sell_weight)) - 1)} else
if(prev_holding==1 & signal=="B"){investment_return=Close/raw_data$Close[t-1]-1} else
if(prev_holding==1 & signal=="H"){investment_return=Close/raw_data$Close[t-1]-1} else
{investment_return = Open/raw_data$Close[t-1]-1}
# save the investment return to the dataset
raw_data$Inv_return[t] = investment_return}
# Generate a holding status column
# and investment return column
dal_df$holding = 0 # first record, you are not holding
dal_df$Inv_return = NA # obviously the return will also be 0
# we can begin the loop
for (t in 100:nrow(dal_df)){
if(t==1){prev_holding=0}
else {prev_holding=dal_df$holding[t-1]}
# look at the signal to decide the change of holding status
signal = dal_df$signal[t]
if(signal=="B"){dal_df$holding[t]=1} else
if (signal=="S") {dal_df$holding[t]=0} else
if (signal=="H"){dal_df$holding[t]=prev_holding}
# Now calculate investment return
Open = dal_df$Open[t]
Close = dal_df$Close[t]
High = dal_df$High[t]
Low = dal_df$Low[t]
if(prev_holding==0 & signal=="B"){investment_return=Close/((High-Open)*buy_weight+Open) - 1} else
if(prev_holding==0 & signal=="H"){investment_return=0} else
#if(prev_holding==1 & signal=="S"){investment_return=Close[t-1]/(Open*(1+sell_weight)) - 1} else
#if(prev_holding==0 & signal=="S"){investment_return=(-1)*(Close/(Open*(1+sell_weight)) - 1)} else
if(prev_holding==1 & signal=="B"){investment_return=Close/dal_df$Close[t-1]-1} else
if(prev_holding==1 & signal=="H"){investment_return=Close/dal_df$Close[t-1]-1} else
{investment_return = Open/dal_df$Close[t-1]-1}
# save the investment return to the dataset
dal_df$Inv_return[t] = investment_return}
From the individual daily returns, we must create a cash and number of stocks vector to hold each days current figures.
Calculate and store in cash and n_stock vector:
# starting investment $100,000
initial_wealth = 100000
# create vectors to store updates
raw_data$cash = rep(initial_wealth, length(raw_data$Close))
raw_data$n_stock = rep(0, length(raw_data$Close))
# loop through signals and simulate the trades
for (t in 1:length(raw_data$signal)){
if(t==1){prev_cash = initial_wealth; n_stock = 0
} else
{prev_cash = raw_data$cash[t-1];
n_stock = raw_data$n_stock[t-1]}
signal = raw_data$signal[t]
Open = raw_data$Open[t]
Close = raw_data$Close[t]
High = raw_data$High[t]
Low = raw_data$Low[t]
# is.na to deal with rows at beginning with no signal
if(is.na(signal)==TRUE){
prev_cash = raw_data$cash[t-1]
n_stock = raw_data$n_stock[t-1]
} else
if (signal=="B"){
buy_skid = (High-Open) * buy_weight + Open
trans_prc = buy_skid
n_change = floor(prev_cash/trans_prc)
stock_n = n_stock + n_change
cash = prev_cash - n_change * trans_prc
raw_data$cash[t] = cash
raw_data$n_stock[t] = stock_n
} else
if (signal =="S"){
sell_skid = (Open-Low) * sell_weight + Open
trans_prc = sell_skid
n_change = n_stock
stock_n = n_change - n_stock
cash = prev_cash + n_change * trans_prc
raw_data$cash[t] = cash
raw_data$n_stock[t] = stock_n
}else
if (signal == "H") {
raw_data$cash[t] = raw_data$cash[t-1]
raw_data$n_stock[t] = raw_data$n_stock[t-1]
}
}
# starting investment $100,000
initial_wealth = 100000
# create vectors to store updates
dal_df$cash = rep(initial_wealth, length(dal_df$Close))
dal_df$n_stock = rep(0, length(dal_df$Close))
# loop through signals and simulate the trades
for (t in 1:length(dal_df$signal)){
if(t==1){prev_cash = initial_wealth; n_stock = 0
} else
{prev_cash = dal_df$cash[t-1];
n_stock = dal_df$n_stock[t-1]}
signal = dal_df$signal[t]
Open = dal_df$Open[t]
Close = dal_df$Close[t]
High = dal_df$High[t]
Low = dal_df$Low[t]
# is.na to deal with rows at beginning with no signal
if(is.na(signal)==TRUE){
prev_cash = dal_df$cash[t-1]
n_stock = dal_df$n_stock[t-1]
} else
if (signal=="B"){
buy_skid = (High-Open) * buy_weight + Open
trans_prc = buy_skid
n_change = floor(prev_cash/trans_prc)
stock_n = n_stock + n_change
cash = prev_cash - n_change * trans_prc
dal_df$cash[t] = cash
dal_df$n_stock[t] = stock_n
} else
if (signal =="S"){
sell_skid = (Open-Low) * sell_weight + Open
trans_prc = sell_skid
n_change = n_stock
stock_n = n_change - n_stock
cash = prev_cash + n_change * trans_prc
dal_df$cash[t] = cash
dal_df$n_stock[t] = stock_n
}else
if (signal == "H") {
dal_df$cash[t] = dal_df$cash[t-1]
dal_df$n_stock[t] = dal_df$n_stock[t-1]
}
}
Calculate the cash plus the value of the stock on hand for the last day in the sample.
Print final return from trend following system and buy-and-hold strategy:
[1] "Initial Investment: $ 100000.00"
[1] "Trend-following system Final Return: $ 3017008.55"
## Buy and hold strategy
initial_price = raw_data$Open[1]
final_price = raw_data$Close[max(index(raw_data))]
n_purchased = floor(initial_wealth/initial_price)
total_return = (final_price - initial_price) * n_purchased
print(paste("Buy and hold system Final Return:",sprintf("$ %3.2f" , total_return)))
[1] "Buy and hold system Final Return: $ 4117924.89"
[1] "Initial Investment: $ 100000.00"
[1] "Trend-following system Final Return: $ 70067.89"
## Buy and hold strategy
initial_price_dal = dal_df$Open[1]
final_price_dal = dal_df$Close[max(index(dal_df))]
n_purchased_dal = floor(initial_wealth/initial_price_dal)
total_return_dal = (final_price_dal - initial_price_dal) * n_purchased_dal
print(paste("Buy and hold system Final Return:",sprintf("$ %3.2f" , total_return_dal)))
[1] "Buy and hold system Final Return: $ 162001.84"
Date Open High Low Close Volume Price
3054 2020-03-16 60.4875 64.77 60 60.5525 322423600 59.13219
return log_return slow_ma fast_ma ema_diff max_range
3054 -0.1286471 -0.1377083 67.15978 71.94174 -4.781956 9.4925
atr risk_per_lot MACD_direction signal holding Inv_return
3054 5.212188 26.06094 Decreasing H 1 -0.128647
cash n_stock
3054 16.21134 22389
max_gain
Date Open High Low Close Volume Price
3053 2020-03-13 66.2225 69.98 63.2375 69.4925 370732000 67.8625
return log_return slow_ma fast_ma ema_diff max_range
3053 0.1198083 0.1131576 67.24847 72.89084 -5.64237 7.922504
atr risk_per_lot MACD_direction signal holding Inv_return
3053 4.774375 23.87188 Decreasing H 1 0.1198083
cash n_stock
3053 16.21134 22389
# Create arrow annotations
# Upper annotations
h_a <- list(
x = max_gain$Date,
y = max_gain$Price,
text = "Largest Gain",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "green",
arrowhead = 5,
arrowsize = 0.8,
arrowwidth = 2.5,
opacity = 0.75,
align = "right",
ax = 5,
ay = -55
)
# Lower annotations
l_a <- list(
x = max_loss$Date,
y = max_loss$Price,
text = "Largest Loss",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "green",
arrowhead = 5,
arrowsize = 0.8,
arrowwidth = 2.5,
opacity = 0.75,
align = "right",
ax = -5,
ay = 55
)
# figure labels
f <- list(
family = "Courier New, monospace",
size = 18,
color = "#7f7f7f ")
x <- list(
title = "x Axis",
titlefont = f)
y <- list(
title = "y Axis",
titlefont = f)
fig <- max_data %>% plot_ly(x = ~Date, type="ohlc",
open = ~Open, close = ~Close,
high = ~High, low = ~Low, name = "AAPL",
increasing = i, decreasing = d)
# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing
# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
line = list(color = '#ccc', width = 0.5),
legendgroup = "Bands", inherit = F,
showlegend = TRUE, hoverinfo = "none")
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
line = list(color = '#E377C2', width = 0.5),
hoverinfo = "none", inherit = F)
# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))
# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)
# Add title
fig <- fig %>% layout(title = "AAPL Stock - Strategy Largest 1-Day Gain",
xaxis = list(rangeslider = list(visible = F)))
fig
fig <- min_data %>% plot_ly(x = ~Date, type="ohlc",
open = ~Open, close = ~Close,
high = ~High, low = ~Low, name = "AAPL",
increasing = i, decreasing = d)
# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing
# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
line = list(color = '#ccc', width = 0.5),
legendgroup = "Bands", inherit = F,
showlegend = TRUE, hoverinfo = "none")
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
line = list(color = '#E377C2', width = 0.5),
hoverinfo = "none", inherit = F)
# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))
# Add arrow annotations
fig <- fig %>% layout(annotations = l_a)
# Add title
fig <- fig %>% layout(title = "AAPL Stock - Strategy Largest 1-day Loss",
xaxis = list(rangeslider = list(visible = F)))
fig
Retrieve the days with the largest single-day loss, gain and average daily return for a simple buy and hold system, as well as for our trend-following system.
Compare the results from the two strategies.
max_daily_return = round((max(raw_data$Inv_return, na.rm = TRUE)*100),4)
max_daily_loss = round((min(raw_data$Inv_return, na.rm = TRUE)*100), 4)
avg_daily_return = round((mean(raw_data$Inv_return, na.rm = TRUE)*100),4)
bh_max_return = round((max(raw_data$return, na.rm = TRUE)*100),4)
bh_max_loss = round((min(raw_data$return, na.rm = TRUE)*100), 4)
bh_avg_return = round((mean(raw_data$return, na.rm = TRUE)*100),4)
print(paste("Largest 1-day return with trend system:", max_daily_return,"%"))
[1] "Largest 1-day return with trend system: 11.9808 %"
[1] "Largest 1-day loss with trend system: -12.8647 %"
[1] "Average 1-day with trend system return: 0.0995 %"
[1] "Largest 1-day return buy and hold strategy: 13.905 %"
[1] "Largest 1-day loss with buy and hold strategy: -17.9195 %"
[1] "Average 1-day with buy and hold strategy: 0.1169 %"
Create a histogram of returns for a simple buy and hold strategy, as well as for our trend-following system:
p <- raw_data %>%
ggplot(aes(x=Inv_return)) +
geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 500)) +
labs(x = "Investment Returns", y = "Frequency of Returns",
title = "AAPL EMA Crossover - Histogram of Investment Returns",
caption = "Trend following system - Slow lag: 150 periods, Fast lag: 25 periods")+
theme_classic()
p
bh = raw_data %>%
ggplot(aes(x=return)) +
geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 500)) +
labs(x = "Investment Returns", y = "Frequency of Returns",
title = "AAPL Buy / Hold - Histogram of Investment Returns",
caption = "This histogram considers a buy and hold strategy")+
theme_classic()
bh
# ggsave("aapl_returns_hist.png", plot = p)
# ggsave("aapl_buy_hold_returns.png", plot = bh)
p_dal <- dal_df %>%
ggplot(aes(x=Inv_return)) +
geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 280)) +
labs(x = "Investment Returns", y = "Frequency of Returns",
title = "DAL EMA Crossover - Histogram of Investment Returns",
caption = "Trend following system - Slow lag: 150 periods, Fast lag: 25 periods")+
theme_classic()
p_dal
# ggsave("dal_returns_hist.png", plot = p_dal)
Retrieve the average daily return and volatility of our trend-following system, as well as a simple buy and hold strategy:
[1] "Average return: 0.000995"
[1] "Volatility: 0.014908"
[1] "Average return buy hold system: 0.001169"
[1] "Volatility buy hold system: 0.019597"
[1] "Buy and hold strategy experiences larger volatility for a small amount of additional return"
[1] "DAL Average return: -5.8e-05"
[1] "DAL Volatility: 0.018017"
[1] "As we can see, the average return when using this system for DAL is much smaller and the volatility is larger. We can expect the Sharpe ratio to be much smaller in this environment"
The Sharpe ratio was developed by Nobel laureate William F. Sharpe and is used to help investors understand the return of an investment compared to its risk.12 The ratio is the average return earned in excess of the risk-free rate per unit of volatility or total risk. Volatility is a measure of the price fluctuations of an asset or portfolio.
[1] "AAPL Trend following: Sharpe ratio of daily returns: 0.066"
[1] "AAPL Buy & hold: Sharpe ratio of daily returns: 0.059"
[1] "Our AAPL Trend-following system achieves a slightly higher Sharpe ratio"
[1] "DAL Trend following: Sharpe ratio of daily returns: -0.00377"
print(paste("Compared to Apple Trend-following returns, DAL's example provides a much smaller Sharpe ratio - this is due to whipsaw-like nature of Delta's historical stock price and the trend-following's lack of ability to trade in this type of environment (at least with the parameters we have chosen)"))
[1] "Compared to Apple Trend-following returns, DAL's example provides a much smaller Sharpe ratio - this is due to whipsaw-like nature of Delta's historical stock price and the trend-following's lack of ability to trade in this type of environment (at least with the parameters we have chosen)"
VAR measures the cut-off return that your financial asset will fall below with certain probability.
Value at risk (VaR) is a statistic that quantifies the extent of possible financial losses within a firm, portfolio, or position over a specific time frame. This metric is most commonly used by investmentand commercial banks to determine the extent and probabilities of potential losses in their institutional portfolios.
[1] "Apple Trend-following VAR: we can expect 95% of the time returns will be greater than: -2.3029"
[1] "Apple Buy / Hold model VAR: we can expect 95% of the time returns will be greater than: -2.8991"
[1] "DAL Trend-following VAR: we can expect 95% of the time returns will be greater than: -2.901"
In order to break down risk into two components, we need to collect what part of the return was attributed to the market and what part to our trading system.
Our \(y\) is the excess return of the trading system
Our \(x\) is the excess return of the market
y = raw_data$Inv_return - rf
x = SPX$return - rf
Call:
lm(formula = y ~ x)
Residuals:
Min 1Q Median 3Q Max
-0.084280 -0.006368 -0.000612 0.006285 0.099440
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.0007526 0.0002058 3.656 0.000259 ***
x 0.5856195 0.0159334 36.754 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.01285 on 3899 degrees of freedom
(99 observations deleted due to missingness)
Multiple R-squared: 0.2573, Adjusted R-squared: 0.2571
F-statistic: 1351 on 1 and 3899 DF, p-value: < 2.2e-16
[1] "Trend-following system Beta: 0.5856"
The larger the \(BETA\) is, the larger the systematic risk is.
We use this to measure the aggressiveness of the stock or the trading system.
Our trend-following system is mostly conservative.
The regression model’s intercept term tells us how much the strategy gives us extra reward, above the market return.
Generally we would like to see a positive \(ALPHA\)
This is associated with its statistical significance.
The higher \(ALPHA\) the better the investment performance
[1] "Jensen's Alpha: 8e-04"
[1] "Treynor's Ratio Version 1: 0.0013"
[1] "Treynor's Ratio Version 2: 0.0017"
raw_data$portfolio_value = raw_data$n_stock*raw_data$Close + raw_data$cash
# Create arrow annotations for trade entry signal
# AAPL
# Upper annotations
h_a <- list(
x = h$Date,
y = h$slow_ma,
text = "Sell",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "red",
arrowhead = 5,
arrowsize = 1,
arrowwidth = 1.7,
opacity = 0.75,
align = "left",
ax = 5,
ay = -55
)
# Lower annotations
l_a <- list(
x = l$Date,
y = l$slow_ma,
text = "Buy",
xref = "x",
yref = "y",
showarrow = TRUE,
arrowcolor = "green",
arrowhead = 5,
arrowsize = 1,
arrowwidth = 1.7,
opacity = 0.75,
align = "right",
ax = -5,
ay = 55
)
# figure labels
f <- list(
family = "Courier New, monospace",
size = 24,
color = "#7f7f7f ")
x <- list(
title = "x Axis",
titlefont = f)
y <- list(
title = "y Axis",
titlefont = f)
# Plot main data
fig <- raw_data %>% plot_ly(x = ~Date, type="ohlc",
open = ~Open, close = ~Close,
high = ~High, low = ~Low, name = "AAPL",
increasing = i, decreasing = d)
# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow EMA",
line = list(color = '#ccc', width = 0.75),
legendgroup = "Bands", inherit = F,
showlegend = TRUE, hoverinfo = "none")
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast EMA",
line = list(color = '#E377C2', width = 0.75),
legendgroup = "bands",
hoverinfo = "none", inherit = F)
ay <- list(
tickfont = list(color = "grey"),
overlaying = "y",
side = "right",
title = "<b>Secondary:</b> Portfolio Value")
fig <- fig %>% add_lines(x = ~Date, y = ~portfolio_value, name = "Portfolio Value",
line = list(color = 'limegreen', width = 1), dash = 'dot', yaxis = "y2")
# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))
# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)
fig <- fig %>% layout(annotations = l_a)
# Add title
fig <- fig %>% layout(
title = "<b>Trend-Following System:</b> Portfolio Value", yaxis2 = ay,
xaxis = list(rangeslider = list(visible = F)),
yaxis = list(title = "<b>Primary:</b> Stock Price")
) %>%
layout(plot_bgcolor = 'rgb(255, 255, 255)',
xaxis = list(
zerolinecolor = '#ffffff',
zerolinewidth = 2,
gridcolor = '#ffffff'),
yaxis = list(
zerolinecolor = '#ffffff',
zerolinewidth = 2,
gridcolor = '#ffffff')
)
fig